import subprocess
import sys
import asyncio
import aiohttp
import requests
import re
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from PyQt5.QtGui import QPixmap, QMouseEvent
from PyQt5.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QLabel, QScrollArea,
    QLineEdit, QHBoxLayout, QSizePolicy, QMessageBox
)
from bs4 import BeautifulSoup
from pypinyin import pinyin, lazy_pinyin, Style

DOUBAN_URL = "https://movie.douban.com/top250"


# ----------------- 数据抓取线程 -----------------
class DoubanFetcher(QThread):
    dataFetched = pyqtSignal(list)  # 发送 [(title, image_url), ...]
    errorOccurred = pyqtSignal(str)

    def run(self):
        asyncio.run(self.fetch_data())

    async def fetch_data(self):
        movies = []
        try:
            async with aiohttp.ClientSession() as session:
                for start in range(0, 250, 25):
                    url = f"{DOUBAN_URL}?start={start}"
                    async with session.get(url, headers={
                        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
                    }) as resp:
                        if resp.status == 200:
                            html = await resp.text()
                            soup = BeautifulSoup(html, "html.parser")
                            for item in soup.select(".item"):
                                title_elem = item.select_one(".title")
                                img_elem = item.select_one(".pic img")
                                if title_elem and img_elem:
                                    title = title_elem.get_text(strip=True)
                                    img_url = img_elem["src"]
                                    movies.append((title, img_url))
                        else:
                            self.errorOccurred.emit(f"请求失败: HTTP {resp.status}")
                            return
            self.dataFetched.emit(movies)
        except Exception as e:
            self.errorOccurred.emit(f"网络错误: {str(e)}")


# ----------------- 图片加载线程 -----------------
class ImageLoader(QThread):
    imageLoaded = pyqtSignal(str, QPixmap)  # title, pixmap
    loadFailed = pyqtSignal(str)

    def __init__(self, title, url):
        super().__init__()
        self.title = title
        self.url = url

    def run(self):
        asyncio.run(self.fetch_image())

    async def fetch_image(self):
        try:
            async with aiohttp.ClientSession() as session:
                async with session.get(self.url, headers={
                    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
                }) as resp:
                    if resp.status == 200:
                        data = await resp.read()
                        pixmap = QPixmap()
                        if pixmap.loadFromData(data):
                            self.imageLoaded.emit(self.title, pixmap)
                        else:
                            self.loadFailed.emit(self.title)
                    else:
                        self.loadFailed.emit(self.title)
        except:
            self.loadFailed.emit(self.title)


# ----------------- 单条电影条目 -----------------
class MovieItem(QWidget):
    clicked = pyqtSignal(str)  # 点击时发送电影标题

    def __init__(self, title, img_url):
        super().__init__()
        self.title = title
        self.setup_ui()

        # 设置可点击样式
        self.setCursor(Qt.PointingHandCursor)
        self.setStyleSheet("""
            MovieItem:hover {
                background-color: #f0f0f0;
            }
        """)

        # 异步加载图片
        self.loader = ImageLoader(title, img_url)
        self.loader.imageLoaded.connect(self.set_image)
        self.loader.loadFailed.connect(self.on_image_load_failed)
        self.loader.start()

    def setup_ui(self):
        layout = QHBoxLayout(self)
        layout.setContentsMargins(8, 6, 8, 6)
        layout.setSpacing(12)

        # 缩略图
        self.img_label = QLabel()
        self.img_label.setFixedSize(70, 100)
        self.img_label.setStyleSheet("""
            background-color: #f5f5f5;
            border-radius: 4px;
            border: 1px solid #ddd;
            color: #999;
        """)
        self.img_label.setAlignment(Qt.AlignCenter)
        self.img_label.setText("加载中...")

        # 标题
        self.title_label = QLabel(self.title)
        self.title_label.setWordWrap(True)
        self.title_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
        self.title_label.setStyleSheet("font-size: 14px;")

        layout.addWidget(self.img_label)
        layout.addWidget(self.title_label, 1)

    def mousePressEvent(self, event: QMouseEvent):
        if event.button() == Qt.LeftButton:
            self.clicked.emit(self.title)
        super().mousePressEvent(event)

    def set_image(self, title, pixmap):
        if title == self.title:
            scaled_pixmap = pixmap.scaled(
                self.img_label.width() - 4,
                self.img_label.height() - 4,
                Qt.KeepAspectRatio,
                Qt.SmoothTransformation
            )
            self.img_label.setPixmap(scaled_pixmap)
            self.img_label.setText("")

    def on_image_load_failed(self, title):
        if title == self.title:
            self.img_label.setText("加载失败")

    def cleanup(self):
        if self.loader.isRunning():
            self.loader.quit()
            self.loader.wait()


# ----------------- M3U8 工具函数 -----------------
class M3U8Player:
    @staticmethod
    def get_index_m3u8(name):
        url = rf"https://www.qingjie123.com/nmuy/{name}/1-1.html"
        headers = {
            "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
            "accept-encoding": "gzip, deflate, br",
            "accept-language": "zh-CN,zh;q=0.9",
            "cache-control": "no-cache",
            "pragma": "no-cache",
            "sec-ch-ua": '"Not;A=Brand";v="99", "Google Chrome";v="139", "Chromium";v="139"',
            "sec-ch-ua-mobile": "?0",
            "sec-ch-ua-platform": '"Windows"',
            "sec-fetch-dest": "document",
            "sec-fetch-mode": "navigate",
            "sec-fetch-site": "none",
            "sec-fetch-user": "?1",
            "upgrade-insecure-requests": "1",
            "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",
        }

        try:
            resp = requests.get(url, headers=headers, timeout=10)
            resp.raise_for_status()

            # 用正则匹配 index.m3u8 字段
            m = re.search(r'"url":"(.*?)"', resp.text)
            if m:
                url = m.group(1)
                return url.replace("\\/", "/")
            return None
        except Exception as e:
            print(f"获取index.m3u8失败: {e}")
            return None

    @staticmethod
    def get_mixed_m3u8(url):
        if not url:
            return None

        headers = {
            "Accept": "*/*",
            "Accept-Encoding": "gzip, deflate, br",
            "Accept-Language": "zh-CN,zh;q=0.9",
            "Cache-Control": "no-cache",
            "Origin": "https://www.qingjie123.com",
            "Pragma": "no-cache",
            "Sec-Fetch-Dest": "empty",
            "Sec-Fetch-Mode": "cors",
            "Sec-Fetch-Site": "cross-site",
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36"
        }

        try:
            resp = requests.get(url, headers=headers, timeout=10)
            resp.raise_for_status()

            lines = resp.text.splitlines()
            ts_files = [line for line in lines if line and not line.startswith("#")]
            if ts_files:
                return f"https://cdn5.yzzy-online.com/20220605/21944_d5dac83f/{ts_files[0]}"
            return None
        except Exception as e:
            print(f"获取mixed.m3u8失败: {e}")
            return None

    @staticmethod
    def play_video(m3u8_url):
        if not m3u8_url:
            return False

        try:
            subprocess.run([
                "ffplay",
                "-i", m3u8_url,
                "-autoexit",
                "-stats",
                "-x", "1280",
                "-y", "720",
                "-analyzeduration", "1000000",
                "-fflags", "nobuffer",
                "-infbuf"
            ], check=True)
            return True
        except subprocess.CalledProcessError:
            print("ffplay播放失败")
            return False
        except FileNotFoundError:
            print("未找到ffplay，请安装FFmpeg")
            return False


# ----------------- 主窗口 -----------------
class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("豆瓣电影 Top250")
        self.resize(500, 750)
        self.setup_ui()
        self.load_data()

    def setup_ui(self):
        layout = QVBoxLayout(self)
        layout.setSpacing(10)
        layout.setContentsMargins(12, 12, 12, 12)

        # 搜索框
        self.search_box = QLineEdit()
        self.search_box.setPlaceholderText("搜索电影标题（留空显示全部）")
        self.search_box.textChanged.connect(self.filter_results)
        layout.addWidget(self.search_box)

        # 状态标签
        self.status_label = QLabel("正在加载数据...")
        self.status_label.setStyleSheet("color: #666; font-size: 12px;")
        layout.addWidget(self.status_label)

        # 滚动区域
        self.scroll_area = QScrollArea()
        self.scroll_area.setWidgetResizable(True)
        self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

        self.scroll_content = QWidget()
        self.scroll_layout = QVBoxLayout(self.scroll_content)
        self.scroll_layout.setAlignment(Qt.AlignTop)
        self.scroll_layout.setSpacing(4)
        self.scroll_area.setWidget(self.scroll_content)
        layout.addWidget(self.scroll_area)

        self.all_data = []
        self.cards = []

    def load_data(self):
        self.status_label.setText("正在加载豆瓣电影数据...")
        self.fetcher = DoubanFetcher()
        self.fetcher.dataFetched.connect(self.show_results)
        self.fetcher.errorOccurred.connect(self.show_error)
        self.fetcher.start()

    def show_results(self, data):
        self.all_data = data
        self.status_label.setText(f"加载完成，共 {len(data)} 部电影")
        self.display_cards(data)

    def show_error(self, error_msg):
        self.status_label.setText("加载失败")
        QMessageBox.warning(self, "错误", error_msg)

    def display_cards(self, data):
        # 清空旧卡片
        for card in self.cards:
            card.cleanup()
            card.deleteLater()
        self.cards.clear()

        if not data:
            no_data_label = QLabel("没有找到电影数据")
            no_data_label.setAlignment(Qt.AlignCenter)
            no_data_label.setStyleSheet("color: #999; padding: 20px;")
            self.scroll_layout.addWidget(no_data_label)
            return

        for title, img_url in data:
            card = MovieItem(title, img_url)
            card.clicked.connect(self.on_movie_clicked)
            self.scroll_layout.addWidget(card)
            self.cards.append(card)

    def on_movie_clicked(self, title):
        """电影被点击时的处理"""
        print(f"选中的电影: {title}")

        self.status_label.setText("正在获取视频链接...")
        QApplication.processEvents()

        index_m3u8 = M3U8Player.get_index_m3u8(''.join(lazy_pinyin(title)))
        if index_m3u8:
            mixed_m3u8 = M3U8Player.get_mixed_m3u8(index_m3u8)
            if mixed_m3u8:
                self.status_label.setText("正在播放...")
                success = M3U8Player.play_video(mixed_m3u8)
                if not success:
                    self.status_label.setText("播放失败")
            else:
                self.status_label.setText("获取视频流失败")
                QMessageBox.warning(self, "错误", "无法获取视频流")
        else:
            self.status_label.setText("获取视频链接失败")
            QMessageBox.warning(self, "错误", "无法获取视频链接")

    def filter_results(self, text):
        text = text.strip().lower()
        if not text:
            filtered = self.all_data
        else:
            filtered = [(t, url) for t, url in self.all_data if text in t.lower()]
        self.display_cards(filtered)

    def closeEvent(self, event):
        # 释放资源
        for card in self.cards:
            card.cleanup()
        if hasattr(self, 'fetcher') and self.fetcher.isRunning():
            self.fetcher.quit()
            self.fetcher.wait()
        event.accept()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())